//********************************************************************************************************
// Product Name: MapWindow.dll Alpha
// Description:  The core libraries for the MapWindow 6.0 project.
//
//********************************************************************************************************
// The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); 
// you may not use this file except in compliance with the License. You may obtain a copy of the License at 
// http://www.mozilla.org/MPL/ 
//
// Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF 
// ANY KIND, either expressed or implied. See the License for the specificlanguage governing rights and 
// limitations under the License. 
//
// The Original Code is MapWindow.dll for the MapWindow 6.0 project
//
// The Initial Developer of this Original Code is Ted Dunsford. Created in January, 2008.
// 
// Contributor(s): (Open source contributors should list themselves and their modifications here). 
//
//********************************************************************************************************
using System;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using MapWindow.Geometries;
namespace MapWindow.DirectX
{
    /// <summary>
    /// This is a convenient structure for organizing the 3D view information
    /// </summary>
    public class Camera 
    {
        private float _aspect;
        private Vector _cameraLocation;
        private Vector3 _cameraUpDirection;
        private Vector _targetLocation;
        private Vector _initialCameraLocation;
        private Vector3 _initialCameraUpDirection;
        private Vector _initialTargetLocation;
        private Matrix _worldMatrix;
        private float _minimumDistance;
        private float _maximumDistance;
        private DrawBox _drawBox;

        #region Methods

        /// <summary>
        /// Creates a new camera that will use the specified factory to create vectors or matrices.
        /// </summary>
        public Camera()
        {
            _drawBox = new DrawBox(-180d, 180d, -90d, 90d, -100d, 100d);
            _cameraLocation = new Vector(0, 0, 100);
            _cameraUpDirection = new Vector3(0, 0, 1);
            _targetLocation = new Vector(0, 0, 0);
            _initialCameraLocation = new Vector(0, 0, 100);
            _initialCameraUpDirection = new Vector3(0, 0, 1);
            _initialTargetLocation = new Vector(0, 0, 0);
            _worldMatrix = Matrix.Identity;
        }


        /// <summary>
        /// Resets the view to whatever the initial values are
        /// </summary>
        public void Reset()
        {
            
            _targetLocation = _initialTargetLocation;
            _cameraLocation = _initialCameraLocation;
            _cameraUpDirection = _initialCameraUpDirection;
            _minimumDistance = .00001f;
            _maximumDistance = 1000f;
            _worldMatrix = Matrix.Identity;
        }

       
        /// <summary>
        /// Given a device, this will position the camera for that device by adjusting the appropriate matrix values
        /// </summary>
        /// <param name="device"></param>
        public void Position(Device device)
        {
            device.Transform.World = _worldMatrix;
            Vector3 camPos = CameraPosition;
            Vector3 targPox = TargetPosition;

            device.Transform.View = Matrix.LookAtLH(CameraPosition, TargetPosition, _cameraUpDirection);
            //if (this.View.Length() < Math.Abs(_cameraLocation.Z))
            //{
            //    _maximumDistance = Math.Abs(Convert.ToSingle(_cameraLocation.Z) * 5f);
            //}
            //else
            //{
            //    _maximumDistance = Math.Abs(this.View.Length() * 5);
            //}
            //_minimumDistance = _maximumDistance / 100;

            device.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI / 4, _aspect, .0001f , 1000f);
        }

        /// <summary>
        /// This adjusts the draw box so that 0 to 1 should range from the left of the screen to the right of the screen.
        /// </summary>
        public void ResetDrawBox()
        {
            double x = _cameraLocation.X;
            double y = _cameraLocation.Y;
            double z = Math.Abs(_cameraLocation.Z);
            _drawBox = new DrawBox(x - z, x + z, y - z, y + z, -z, z);
            CameraPosition = new Vector3(.5f, .5f, -1f);
            //TargetPosition = new Vector3(.5f, .5f, 0f);
        }

        /// <summary>
        /// Ratio is the ratio of the new distance to the previous distance.
        /// </summary>
        /// <param name="ratio">A float describing the factor to modify the distance by</param>
        public void Zoom(float ratio)
        {

            Vector v = Vector.Subtract(_cameraLocation, _targetLocation);
            v = v.Multiply(ratio);
            _cameraLocation = v.Add(_targetLocation);
        }


        #endregion


        #region Properties

        /// <summary>
        /// This is the width of the control divided by the height of the control
        /// </summary>
        public float Aspect
        {
            get { return _aspect; }
            set { _aspect = value; }
        }

        /// <summary>
        /// Controls the three dimensional position of the camera that is viewing the world
        /// </summary>
        public Vector CameraLocation
        {
            get { return _cameraLocation; }
            set { _cameraLocation = value; }
        }

        /// <summary>
        /// Gets or sets the model space camera position, instead of the geographic location.
        /// </summary>
        public Vector3 CameraPosition
        {
            get { return _drawBox.ProjToView(_cameraLocation); }
            set 
            {
                double oldX = _cameraLocation.X;
                _cameraLocation = _drawBox.ViewToProjVector(value);
                double newX = _cameraLocation.X;

            }
        }

        /// <summary>
        /// Controls the direction that coresponds to directly "up" for the camera
        /// </summary>
        public Vector3 CameraUpDirection
        {
            get { return _cameraUpDirection; }
            set { _cameraUpDirection = value; }
        }

        /// <summary>
        /// 
        /// </summary>
        public DrawBox DrawBox
        {
            get { return _drawBox; }
            set { _drawBox = value; }
        }

        /// <summary>
        /// Used to help reset the view
        /// </summary>
        public Vector InitialCameraLocation
        {
            get { return _initialCameraLocation; }
            set { _initialCameraLocation = value; }
        }

        /// <summary>
        /// Used to help reset the view
        /// </summary>
        public Vector3 InitialCameraUpDirection
        {
            get { return _initialCameraUpDirection; }
            set { _initialCameraUpDirection = value; }
        }

        /// <summary>
        /// Used to help reset the view
        /// </summary>
        public Vector InitialTargetLocation
        {
            get { return _initialTargetLocation; }
            set { _initialTargetLocation = value; }
        }

        /// <summary>
        /// Specifies the near plane distance from the camera
        /// </summary>
        public float MinimumDistance
        {
            get { return _minimumDistance; }
            set { _minimumDistance = value; }
        }

        /// <summary>
        /// Specifies the far plane distance from the camera
        /// </summary>
        public float MaximumDistance
        {
            get { return _maximumDistance; }
            set { _maximumDistance = value; }
        }

        /// <summary>
        /// This is a target location for the view to be focusing on.
        /// </summary>
        public Vector TargetLocation
        {
            get { return _targetLocation; }
            set { _targetLocation = value; }
        }

        /// <summary>
        /// Gets or sets the position in model space coordinates rather than geographic coordinates
        /// </summary>
        public Vector3 TargetPosition
        {
            get 
            {
                Vector3 temp = _drawBox.ProjToView(_targetLocation);
                temp.Z = 0f;
                return temp;
            }
            set { _targetLocation = _drawBox.ViewToProjVector(value); }
        }

        /// <summary>
        /// This returns a plane that is normal to the view direction
        /// and intersects the target point.
        /// </summary>
        public Plane TargetPlane
        {
            get
            {
                Vector3 v = Vector3.Subtract(CameraPosition, TargetPosition);
                return new Plane(v.X, v.Y, v.Z, v.Length());
            }
            
        }

        /// <summary>
        /// This should return the plane that shows 0 elevation
        /// </summary>
        public Plane XYPlane
        {
            get
            {
                Vector3 v = Vector3.Subtract(CameraPosition, TargetPosition);
                return new Plane(0, 0, -1, v.Z);
            }
        }

        /// <summary>
        /// Obtains a vector describing both the distance and direction from the camera to the target
        /// </summary>
        public Vector3 View
        {
            get
            {
                return Vector3.Subtract(TargetPosition, CameraPosition);
            }
        }

        



        /// <summary>
        /// This is the matrix to use in order to scale or alter the world
        /// </summary>
        public Matrix WorldMatrix
        {
            get { return _worldMatrix; }
            set { _worldMatrix = value; }
        }

      


        #endregion

    }

    
}
